import java.awt.Color
import com.sksamuel.scrimage.color.RGBColor

def dot(u: Vec3, v: Vec3): Double = u.x * v.x + u.y * v.y + u.z * v.z
def cross(u: Vec3, v: Vec3): Vec3 = Vec3(
    u.y * v.z - u.z * v.y,
    u.z * v.x - u.x * v.z,
    u.x * v.y - u.y * v.x
  )

case class Vec3(x: Double, y: Double, z: Double):
  def negate: Vec3 = Vec3(-x, -y, -z)
  def apply(i: Int) = i match
    case 0 => x
    case 1 => y
    case 2 => z
    case _ => throw IndexOutOfBoundsException()
  def +(that: Vec3): Vec3 = Vec3(x + that.x, y + that.y, z + that.z)
  def -(that: Vec3): Vec3 = Vec3(x - that.x, y - that.y, z - that.z)
  def *(v: Double): Vec3 = Vec3(x * v, y * v, z * v)
  def *(v: Vec3): Vec3 = Vec3(x * v.x, y * v.y, z * v.z)
  def /(v: Double): Vec3 = Vec3(x / v, y / v, z / v)

  def length: Double = math.sqrt(length_squared)
  def length_squared: Double = x * x + y * y + z * z
  def unit_vector: Vec3 = this / length

  def to_color(samples_per_pixel: Int): Color =
    val r = 256.0 * clamp(math.sqrt(x / samples_per_pixel), 0, 0.999)
    val g = 256.0 * clamp(math.sqrt(y / samples_per_pixel), 0, 0.999)
    val b = 256.0 * clamp(math.sqrt(z / samples_per_pixel), 0, 0.999)
    RGBColor(r.toInt, g.toInt, b.toInt).awt

  def near_zero: Boolean =
    val s = 1e-8
    (math.abs(x) < s) && (math.abs(y) < s) && (math.abs(z) < s)

object Vec3:
  val rng = scala.util.Random()

  def random: Vec3 = 
    Vec3(rng.nextDouble, rng.nextDouble, rng.nextDouble)

  def random(min: Double, max: Double): Vec3 =
    val x = rng.between(min, max)
    val y = rng.between(min, max)
    val z = rng.between(min, max)
    Vec3(x, y, z)

  def random_in_unit_sphere: Vec3 =
    var p = random(-1, 1)
    while p.length_squared >= 1 do
      p = random(-1, 1)
    p

  def random_unit_vector: Vec3 =
    random_in_unit_sphere.unit_vector

  def random_in_hemisphere(normal: Vec3): Vec3 =
    val in_unit_sphere = random_in_unit_sphere
    if dot(in_unit_sphere, normal) > 0 then
      in_unit_sphere
    else
      in_unit_sphere * -1

  def random_in_unit_disk: Vec3 =
    var p = Vec3(rng.between(-1.0, 1.0), rng.between(-1.0, 1.0), 0)
    while p.length_squared >= 1 do
      p = Vec3(rng.between(-1.0, 1.0), rng.between(-1.0, 1.0), 0)
    p
        